package pers.qianyu.diytomcat.http;

import cn.hutool.core.convert.*;
import cn.hutool.core.io.*;
import cn.hutool.core.util.*;
import pers.qianyu.diytomcat.catalina.*;
import pers.qianyu.diytomcat.util.*;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.net.*;
import java.nio.charset.*;
import java.util.*;

/**
 * @author mizzle rain
 * @date 2021-01-06 22:11
 */
public class Request extends BaseRequest {
    private Socket socket;
    private Context context;
    private Connector connector;

    private String uri;
    private String requestString;
    private String method;
    private Map<String, String[]> parameterMap;
    private Map<String, String> headerMap;
    private Cookie[] cookies;
    private HttpSession session;
    private boolean forward;
    private Map<String, Object> attributesMap;

    public Request(Socket socket, Connector connector) throws IOException {
        this.socket = socket;
        this.connector = connector;
        parseRequestString();
        if (StrUtil.isEmpty(requestString)) {
            return;
        }
        parseContext();
        parseUri();
        parseMethod();
        parameterMap = new HashMap<>();
        parseParameters();
        headerMap = new HashMap<>();
        parseHeaders();
        parseCookies();
        attributesMap = new HashMap<>();
    }

    private void parseRequestString() throws IOException {
        InputStream in = socket.getInputStream();
        // 这里必须自定义 readBytes 方法
        byte[] bytes = IOUtils.readBytes(in, false);
        this.requestString = new String(bytes, StandardCharsets.UTF_8);
    }

    private void parseUri() {
        String uri = StrUtil.subBetween(requestString, " ");
        if (uri.contains("?")) {
            this.uri = StrUtil.subBefore(uri, "?", false);
        } else {
            this.uri = uri;
        }
        if (!Objects.equals("/", context.getPath())) {
            this.uri = StrUtil.removePrefix(this.uri, context.getPath());
            if (StrUtil.isEmpty(this.uri)) {
                this.uri = "/";
            }
        }
    }

    private void parseMethod() {
        this.method = StrUtil.subBefore(requestString, " ", false);
    }

    private void parseParameters() {
        String paramString = null;
        if (Objects.equals(method, "GET")) {
            String line = StrUtil.subBetween(requestString, " ");
            paramString = StrUtil.subAfter(line, "?", false);
        } else if (Objects.equals(method, "POST")) {
            paramString = StrUtil.subAfter(requestString, "\r\n\r\n", false);
        }
        if (Objects.isNull(paramString)) {
            return;
        }
        paramString = URLUtil.decode(paramString);
        String[] strings = paramString.split("&");
        for (String str : strings) {
            String[] arr = str.split("=");
            if (arr.length == 2) {
                String key = arr[0];
                String value = arr[1];
                if (parameterMap.containsKey(key)) {
                    String[] arr2 = parameterMap.get(key);
                    ArrayUtil.append(arr2, value);
                    parameterMap.put(key, arr2);
                } else {
                    parameterMap.put(arr[0], new String[]{arr[1]});
                }
            }
        }
    }

    private void parseHeaders() {
        StringReader reader = new StringReader(requestString);
        ArrayList<String> lines = new ArrayList<>();
        IoUtil.readLines(reader, lines);
        lines.remove(0);
        for (String line : lines) {
            if (StrUtil.isNotEmpty(line) && line.contains(":")) {
                String[] split = line.split(":");
                String key = split[0].trim().toLowerCase();
                String value = split[1].trim();
                headerMap.put(key, value);
            }
        }
    }

    private void parseContext() {
        String url = StrUtil.subBetween(requestString, " ");
        String path;
        if (StrUtil.count(url, "/") < 2) {
            path = StrUtil.subAfter(url, "/", false);
        } else {
            path = StrUtil.subBetween(url, "/", "/");
        }
        if (StrUtil.isEmpty(path)) {
            path = "/";
        } else {
            path = "/" + path;
        }
        Service service = connector.getService();
        context = service.getEngine().getDefaultHost().getContext(path);
        if (Objects.isNull(context)) {
            context = service.getEngine().getDefaultHost().getContext("/");
        }
    }

    private void parseCookies() {
        String cookieStr = headerMap.get("cookie");
        if (StrUtil.isBlank(cookieStr)) {
            return;
        }
        String[] strings = StrUtil.split(cookieStr, ";");
        ArrayList<Cookie> list = new ArrayList<>();
        for (String s : strings) {
            s = s.trim();
            if (StrUtil.isNotBlank(s)) {
                String[] arr = s.split("=");
                String key = arr[0].trim();
                String value = arr[1].trim();
                Cookie co = new Cookie(key, value);
                list.add(co);
            }
        }
        cookies = list.toArray(new Cookie[0]);
    }

    @Override
    public String getRealPath(String s) {
        return context.getServletContext().getRealPath(s);
    }

    @Override
    public ServletContext getServletContext() {
        return context.getServletContext();
    }

    @Override
    public String getParameter(String s) {
        String[] arr = parameterMap.get(s);
        if (Objects.nonNull(arr) && arr.length > 0) {
            return arr[0];
        }
        return null;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(parameterMap.keySet());
    }

    @Override
    public String[] getParameterValues(String s) {
        return parameterMap.get(s);
    }

    @Override
    public String getHeader(String s) {
        if (Objects.isNull(s)) {
            return null;
        }
        s = s.toLowerCase();
        return headerMap.get(s);
    }

    @Override
    public Enumeration<String> getHeaderNames() {
        return Collections.enumeration(headerMap.keySet());
    }

    @Override
    public int getIntHeader(String s) {
        s = s.toLowerCase();
        return Convert.toInt(headerMap.get(s), 0);
    }

    public String getJsessionIdFromCookie() {
        if (Objects.isNull(cookies)) {
            return null;
        }
        for (Cookie cookie : cookies) {
            if (Objects.equals(cookie.getName(), "JSESSIONID")) {
                return cookie.getValue();
            }
        }
        return null;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String s) {
        return new ApplicationRequestDispatcher(s);
    }

    @Override
    public void setAttribute(String s, Object o) {
        attributesMap.put(s, o);
    }

    @Override
    public Object getAttribute(String s) {
        return attributesMap.get(s);
    }

    @Override
    public void removeAttribute(String s) {
        attributesMap.remove(s);
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return Collections.enumeration(attributesMap.keySet());
    }

    /*********** 补充 **********/

    @Override
    public String getLocalAddr() {
        return socket.getLocalAddress().getHostAddress();
    }

    @Override
    public String getLocalName() {
        return socket.getLocalAddress().getHostName();
    }

    @Override
    public int getLocalPort() {
        return socket.getLocalPort();
    }

    @Override
    public String getProtocol() {
        return "HTTP/1.1";
    }

    @Override
    public String getRemoteAddr() {
        InetSocketAddress address = (InetSocketAddress) socket.getRemoteSocketAddress();
        String addr = address.getAddress().toString();
        return StrUtil.subAfter(addr, "/", false);
    }

    @Override
    public String getRemoteHost() {
        InetSocketAddress address = (InetSocketAddress) socket.getRemoteSocketAddress();
        return address.getHostName();
    }

    @Override
    public int getRemotePort() {
        return socket.getPort();
    }

    @Override
    public String getScheme() {
        return "http";
    }

    @Override
    public String getServerName() {
        return getHeader("host");
    }

    @Override
    public int getServerPort() {
        return getLocalPort();
    }

    @Override
    public String getContextPath() {
        String path = this.context.getPath();
        if (Objects.equals("/", path)) {
            return "";
        }
        return path;
    }

    @Override
    public String getRequestURI() {
        return uri;
    }

    @Override
    public StringBuffer getRequestURL() {
        StringBuffer buffer = new StringBuffer();
        int localPort = getServerPort();
        String scheme = getScheme();
        if (localPort < 0) {
            localPort = 80;
        }
        buffer.append(scheme).append("://").append(getServerName());
        if (Objects.equals(scheme, "http") && localPort != 80 ||
                Objects.equals(scheme, "https") && localPort != 443) {
            buffer.append(":").append(localPort);
        }
        buffer.append(uri);
        return buffer;
    }

    /************** automatic generated *****************/
    @Override
    public Cookie[] getCookies() {
        return cookies;
    }

    public void setCookies(Cookie[] cookies) {
        this.cookies = cookies;
    }

    public Socket getSocket() {
        return socket;
    }

    public Map<String, String> getHeaderMap() {
        return headerMap;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public String getRequestString() {
        return requestString;
    }

    public void setRequestString(String requestString) {
        this.requestString = requestString;
    }

    @Override
    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public void setHeaderMap(Map<String, String> headerMap) {
        this.headerMap = headerMap;
    }

    public void setParameterMap(Map<String, String[]> parameterMap) {
        this.parameterMap = parameterMap;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return parameterMap;
    }

    public Context getContext() {
        return context;
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public Connector getConnector() {
        return connector;
    }

    public void setConnector(Connector connector) {
        this.connector = connector;
    }

    @Override
    public HttpSession getSession() {
        return session;
    }

    public void setSession(HttpSession session) {
        this.session = session;
    }

    public boolean isForward() {
        return forward;
    }

    public void setForward(boolean forward) {
        this.forward = forward;
    }

    public Map<String, Object> getAttributesMap() {
        return attributesMap;
    }

    public void setAttributesMap(Map<String, Object> attributesMap) {
        this.attributesMap = attributesMap;
    }
}
